/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许，不可做商业用途！
 *
 * 版权所有，侵权必究！
 */
package com.yami.shop.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.shop.bean.app.dto.OrderCountData;
import com.yami.shop.bean.dto.StatisticsRefundDto;
import com.yami.shop.bean.enums.BuyerReasonType;
import com.yami.shop.bean.model.Order;
import com.yami.shop.bean.param.OrderPayParam;
import com.yami.shop.bean.param.StatisticsRefundParam;
import com.yami.shop.common.i18n.I18nMessage;
import com.yami.shop.common.util.Arith;
import com.yami.shop.common.util.DateUtils;
import com.yami.shop.dao.OrderMapper;
import com.yami.shop.dao.OrderRefundMapper;
import com.yami.shop.service.StatisticsOrderService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lgh on 2018/09/15.
 */
@Service
@AllArgsConstructor
public class StatisticsOrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements StatisticsOrderService {

    private final OrderMapper orderMapper;

    private final OrderRefundMapper orderRefundMapper;

    @Override
    public OrderPayParam getPayUserCountByshopId(Long shopId, Date startTime, Date endTime) {
        // 获取今天的订单统计概况
        OrderPayParam toDayInfo = this.getOrderStatisticsByShopIdAndDateRange(shopId, startTime, endTime);
        // 获取昨天的订单统计概况
        OrderPayParam yesToDayInfo = this.getOrderStatisticsByShopIdAndDateRange(shopId, DateUtil.beginOfDay(DateUtil.yesterday()), DateUtil.endOfDay(DateUtil.yesterday()));
        // 计算今天与昨天数值变化的比率
        toDayInfo.setYesterdayPayUserRate(this.getChangeRate(toDayInfo.getPayUserCount(), yesToDayInfo.getPayUserCount()));
        toDayInfo.setYesterdayPayOrderRate(this.getChangeRate(toDayInfo.getPayOrderCount(), yesToDayInfo.getPayOrderCount()));
        toDayInfo.setYesterdayRefundRate(this.getChangeRate(toDayInfo.getRefund(), yesToDayInfo.getRefund()));
        toDayInfo.setYesterdayOnePriceRate(this.getChangeRate(toDayInfo.getOnePrice(), yesToDayInfo.getOnePrice()));
        return toDayInfo;
    }

    @Override
    public OrderPayParam getActualTotalByHour(Long shopId, Date startTime, Date endTime) {
        List<OrderPayParam> actualTotalByHour = orderMapper.getActualTotalByHour(shopId, startTime, endTime);
        Map<Integer, Double> payMap = new HashMap<>(30);
        for (OrderPayParam orderPay : actualTotalByHour) {
            payMap.put(Integer.parseInt(orderPay.getDates()), orderPay.getPayActualTotal());
        }
        List<OrderPayParam> yesterdayTotalByHour = orderMapper.getActualTotalByHour(shopId, DateUtil.beginOfDay(DateUtil.yesterday()),
                DateUtil.endOfDay(DateUtil.yesterday()));
        Map<Integer, Double> yesterdayPayMap = new HashMap<>(30);
        for (OrderPayParam orderPay : yesterdayTotalByHour) {
            yesterdayPayMap.put(Integer.parseInt(orderPay.getDates()), orderPay.getPayActualTotal());
        }
        double sum = 0.00;
        double yesterdaySum = 0.00;
        List<Double> payYesterdayActualTotal = new ArrayList<>();
        List<Double> nowActualTotal = new ArrayList<>();
        OrderPayParam param = new OrderPayParam();
        for (int i = 0; i < 24; i++) {
            if (payMap.get(i) != null) {
                sum = Arith.add(sum, payMap.get(i));
            }
            if (yesterdayPayMap.get(i) != null) {
                yesterdaySum = Arith.add(yesterdaySum, yesterdayPayMap.get(i));
            }
            payYesterdayActualTotal.add(Arith.roundByBanker(yesterdaySum, 2));
            nowActualTotal.add(Arith.roundByBanker(sum, 2));
        }
        param.setPayYesterdayActualTotal(payYesterdayActualTotal);
        param.setNowActualTotal(nowActualTotal);
        return param;
    }

    @Override
    public List<OrderPayParam> getActualTotalByDay(Long shopId, Date startTime, Date endTime) {
        List<OrderPayParam> actualTotal = orderMapper.getPayOrderByTime(shopId, startTime, endTime);
        List<OrderPayParam> actualTotalByDay = new ArrayList<>();
        //获取本月起始时间
        DateTime dateTime = DateUtil.beginOfMonth(DateUtil.date());
        double sum = 0.00;
        for (OrderPayParam orderPayParam : actualTotal) {
            DateTime time = DateTime.of(orderPayParam.getDates(), "yyyy-MM-dd");
            if (DateUtil.compare(time, dateTime) > 0) {
                sum = Arith.add(sum, orderPayParam.getPayActualTotal());
                orderPayParam.setOnePrice(Arith.roundByBanker(sum, 2));
            }
            actualTotalByDay.add(orderPayParam);
        }
        return actualTotalByDay;
    }


    /**
     * 生成退款信息和比率信息
     *
     * @param shopId 商家id
     */
    @Override
    public StatisticsRefundParam getOrderRefundByShopId(Long shopId, Date startTime, Date endTime) {
        StatisticsRefundParam refundParam = orderRefundMapper.getRefundByShopId(shopId, startTime, endTime);
        OrderPayParam oderParam = getPayUserCountByshopId(shopId, startTime, endTime);
        StatisticsRefundParam yesterdayRefundParam = orderRefundMapper.getRefundByShopId(shopId, DateUtil.beginOfDay(DateUtil.yesterday()),
                DateUtil.endOfDay(DateUtil.yesterday()));
        OrderPayParam yesterdayOderParam = getPayUserCountByshopId(shopId, DateUtil.beginOfDay(DateUtil.yesterday()),
                DateUtil.endOfDay(DateUtil.yesterday()));
        StatisticsRefundParam lastWeekRefundParam = orderRefundMapper.getRefundByShopId(shopId, DateUtil.beginOfDay(DateUtil.lastWeek()),
                DateUtil.endOfDay(DateUtil.lastWeek()));
        OrderPayParam lastWeekOderParam = getPayUserCountByshopId(shopId, DateUtil.beginOfDay(DateUtil.lastWeek()),
                DateUtil.endOfDay(DateUtil.lastWeek()));
        double yesterdayRate = 0.00;
        double lastWeekRate = 0.00;
        double refundRate = 0.00;
        double yesterdayRefundRate = 0.00;
        double lastweekRefundRate = 0.00;
        //金额比率
        if (yesterdayRefundParam.getPayActualTotal() != 0) {
            yesterdayRate = Arith.div(Arith.mul(refundParam.getPayActualTotal(), 100), yesterdayRefundParam.getPayActualTotal(), 2);
        }
        if (lastWeekRefundParam.getPayActualTotal() != 0) {
            lastWeekRate = Arith.div(Arith.mul(refundParam.getPayActualTotal(), 100), lastWeekRefundParam.getPayActualTotal(), 2);
        }
        //退款比率
        if (oderParam.getPayOrderCount() != 0) {
            refundRate = Arith.div(Arith.mul(refundParam.getRefundCount(), 100), oderParam.getPayOrderCount(), 2);
        }
        if (yesterdayOderParam.getPayOrderCount() != 0) {
            yesterdayRefundRate = Arith.div(Arith.mul(refundParam.getRefundCount(), 100), yesterdayOderParam.getPayOrderCount(), 2);
        }
        if (lastWeekOderParam.getPayOrderCount() != 0) {
            lastweekRefundRate = Arith.div(Arith.mul(lastWeekRefundParam.getRefundCount(), 100), lastWeekOderParam.getPayOrderCount(), 2);
        }
        yesterdayRefundRate = Arith.sub(refundRate, yesterdayRefundRate);
        lastweekRefundRate = Arith.sub(refundRate, lastweekRefundRate);
        refundParam.setYesterdayAmountRate(yesterdayRate);
        refundParam.setSevenDayAmountRate(lastWeekRate);
        refundParam.setRefundRate(refundRate);
        refundParam.setYesterdayRefundRate(yesterdayRefundRate);
        refundParam.setSevenDayRefundRate(lastweekRefundRate);
        return refundParam;
    }


    /**
     * 生成退款信息和分段比率信息
     *
     * @param shopId 商家id
     */
    @Override
    public List<StatisticsRefundParam> getOrderRefundByShopIdAndDay(Long shopId, Date startTime, Date endTime) {
        // 统计的天数
        long dayCount = DateUtil.between(startTime, endTime, DateUnit.DAY) + 1;
        Date curTime = this.getStartTime(endTime, (int) dayCount);
        // 根据时间范围查询订单概况与退款数据
        List<StatisticsRefundParam> refundList = orderRefundMapper.getRefundByShopIdAndDay(shopId, startTime, endTime);
        List<OrderPayParam> orderList = orderMapper.getPayOrderByTime(shopId, startTime, endTime);
        Map<String, StatisticsRefundParam> refundMap = refundList.stream().collect(Collectors.toMap(StatisticsRefundParam::getRefundDateToString, o -> o));
        Map<String, OrderPayParam> orderMap = orderList.stream().collect(Collectors.toMap(OrderPayParam::getDates, o -> o));
        // 返回结果
        List<StatisticsRefundParam> refundNewList = new ArrayList<>();
        String dateKey;
        StatisticsRefundParam refundParam;
        OrderPayParam orderPayParam;
        for (int i = 0; i < dayCount; i++) {
            dateKey = DateUtils.dateToStrYmd(curTime);
            refundParam = refundMap.get(dateKey);
            orderPayParam = orderMap.get(dateKey);
            StatisticsRefundParam resItem = new StatisticsRefundParam();
            resItem.setRefundDateToString(dateKey);
            if (Objects.nonNull(refundParam) && Objects.nonNull(orderPayParam)) {
                resItem.setRefundRate(this.getRatioRate(refundParam.getRefundCount(), orderPayParam.getPayOrderCount()));
                resItem.setPayActualTotal(refundParam.getPayActualTotal());
                resItem.setRefundCount(refundParam.getRefundCount());
                resItem.setRefundDate(refundParam.getRefundDate());

                curTime = getNextTime(curTime);
                refundNewList.add(resItem);
                continue;
            }
            if (Objects.isNull(refundParam)) {
                resItem.setRefundRate(0.0);
                resItem.setPayActualTotal(0L);
                resItem.setRefundCount(0);

                curTime = getNextTime(curTime);
                refundNewList.add(resItem);
                continue;
            }
            resItem.setRefundRate(100.0);
            resItem.setPayActualTotal(refundParam.getPayActualTotal());
            resItem.setRefundCount(refundParam.getRefundCount());

            curTime = getNextTime(curTime);
            refundNewList.add(resItem);
        }
        return refundNewList;
    }

    private Date getNextTime(Date startTime){
        startTime = DateUtil.offsetDay(startTime, 1);
        return startTime;
    }

    /**
     * 根据商品名生成退款排行
     *
     * @param statisticsRefundDto
     */
    @Override
    public List<StatisticsRefundParam> getRefundRankingByProd(StatisticsRefundDto statisticsRefundDto) {
        if (Objects.isNull(statisticsRefundDto.getSize())) {
            // pc平台、商家端默认是五条数据
            statisticsRefundDto.setSize(5);
        }
        if (Objects.isNull(statisticsRefundDto.getStartTime()) || Objects.isNull(statisticsRefundDto.getEndTime())) {
            statisticsRefundDto.setStartTime(DateUtil.endOfDay(DateUtil.lastMonth()));
            statisticsRefundDto.setEndTime(DateUtil.endOfDay(DateUtil.date()));
        }
        return orderRefundMapper.getRefundRankingByProd(statisticsRefundDto, I18nMessage.getDbLang());
    }

    /**
     * 根据退款原因生成退款排行
     *
     * @param shopId 商家id
     */
    @Override
    public List<StatisticsRefundParam> getRefundRankingByReason(Long shopId, Date beginTime, Date endTime) {
        List<StatisticsRefundParam> ranking = orderRefundMapper.getRefundRankingByReason(shopId, beginTime, endTime);
        List<StatisticsRefundParam> rank = new ArrayList<>();
        double sum = 0.00;
        for (StatisticsRefundParam refundParam : ranking) {
            sum = Arith.add(sum, refundParam.getPayActualTotal());
        }
        for (StatisticsRefundParam refundParam : ranking) {
            try {
                BuyerReasonType reasonType = BuyerReasonType.instance(Integer.valueOf(refundParam.getBuyerReason()));
                if (Objects.nonNull(reasonType)) {
                    refundParam.setBuyerReason(I18nMessage.getDbLang() == 0 ? reasonType.getCn() : reasonType.getEn());
                }
            } catch (NumberFormatException ignored) {
                //如果出现BuyerReasonType没有的类型，则默认即可
            }
            refundParam.setRefundRate(Arith.div(Arith.mul(refundParam.getPayActualTotal(), 100), sum, 2));
            rank.add(refundParam);
        }
        return rank;
    }

    @Override
    public OrderCountData getOrderCountOfStatusByShopId(Long shopId) {
        return orderMapper.getOrderCountOfStatusByShopId(shopId);
    }

    /**
     * 根据店铺id与时间范围获取订单统计概况信息
     * @param shopId
     * @param startTime
     * @param endTime
     * @return
     */
    private OrderPayParam getOrderStatisticsByShopIdAndDateRange(Long shopId, Date startTime, Date endTime) {
        OrderPayParam orderPayParam = orderMapper.getOrderCountByShopId(shopId, startTime, endTime);
        StatisticsRefundParam statisticsRefundParam = orderRefundMapper.getRefundByShopId(shopId, startTime, endTime);
        double onePrice = (orderPayParam.getPayUserCount() == 0) ?
                0 : (Arith.div(orderPayParam.getPayActualTotal(), orderPayParam.getPayUserCount(), 2));
        orderPayParam.setOnePrice(onePrice);
        orderPayParam.setRefund(statisticsRefundParam.getPayActualTotal());
        return orderPayParam;
    }

    private Double getChangeRate(double nowValue, double oldValue) {
        if (Objects.equals(oldValue, 0.0)) {
            return Objects.equals(nowValue, 0.0) ? 0.0 : 1.0;
        }
        double minus = Arith.sub(nowValue, oldValue);
        return Arith.div(minus, oldValue, 4);
    }

    private Date getStartTime(Date endTime, Integer dayCount) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(endTime);
        calendar.add(Calendar.DATE, -dayCount + 1);
        return DateUtil.beginOfDay(calendar.getTime());
    }

    private Double getRatioRate(Integer dividend, Integer divisor) {
        if (Objects.isNull(dividend) || dividend == 0.0) {
            return 0.0;
        }
        if (Objects.isNull(divisor) || divisor == 0.0) {
            return 100.0;
        }
        return Arith.mul(Arith.div(dividend, divisor, 4), 100.00);
    }

}
